/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "Socket.hpp"
#include "WSAImpl.h"
#include <logs/Log.h>
#include <utl.h>
#include <algorithm>

namespace liba
{
namespace net
{


class LSocket::Impl : public WSAImpl
{};

class RWSocket::Impl : public WSAImpl
{};

class UDPSocket::Impl : public WSAImpl
{
public:
	sockaddr_in last_sender;
};

LSocket::LSocket(int port)
:	impl( new Impl() )
{
	if( !impl->SystemValid() )
		return;
	// Creates listening socket
	impl->socket = socket( PF_INET, SOCK_STREAM, 0 );
	if( !impl->is_valid() )
	{
		int error = WSAGetLastError();
		return;
	}
	// Make up address
	SOCKADDR_IN	sock_addr;
	memset( &sock_addr, 0, sizeof(sock_addr) );
	sock_addr.sin_family		= AF_INET;
	sock_addr.sin_addr.s_addr	= INADDR_ANY;
	sock_addr.sin_port			= htons(port);
	// Bind to the address and port
	if( bind(impl->socket, reinterpret_cast<SOCKADDR*>( &sock_addr ), sizeof(sock_addr)) )
	{
		impl->Close();
		return;
	}
	impl->SetIntOption( SOL_SOCKET, SO_DONTLINGER, 1 );
	impl->SetIntOption( SOL_SOCKET, SO_KEEPALIVE, 1 );
	// establishes a socket to listen for incoming connection
	// so Accept can be called
	if( listen( impl->socket, SOMAXCONN ) )
	{
		impl->Close();
		return;
	}
}

LSocket::~LSocket()
{
	delete impl; impl = 0;
}

bool LSocket::is_valid()const
{
	return impl->is_valid();
}

RWSocket::RWSocket( const LSocket & lsock )
:	impl( new Impl() )
{
	if( !impl->SystemValid() )
		return;
	// waits to accept connection from listening socket
	impl->socket = accept( lsock.impl->socket, NULL, NULL );
	if( !impl->is_valid() )
	{
		DWORD err = WSAGetLastError();
		return;
	}
}

RWSocket::RWSocket(const char * address, int port)
:	impl( new Impl() )
{
	if( !impl->SystemValid() )
		return;
	if( !address )
		return;
	impl->socket = socket(PF_INET, SOCK_STREAM, 0);
	if( !impl->is_valid() )
		return;
	// Fill address machinery of sockets.
	SOCKADDR_IN sock_addr;
	memset( &sock_addr, 0, sizeof(sock_addr) );
	sock_addr.sin_family = AF_INET;
	sock_addr.sin_addr.s_addr = inet_addr(address);
	if( sock_addr.sin_addr.s_addr == INADDR_NONE )
	{
		LPHOSTENT host;
		host = gethostbyname(address);
		if( host != NULL )
			sock_addr.sin_addr.s_addr = ((LPIN_ADDR)host->h_addr)->s_addr;
		else
		{
			WSASetLastError( WSAEINVAL );
			impl->Close();
			return;
		}
	}
	sock_addr.sin_port = htons( (u_short)port );
	// connects to peer
	if( connect( impl->socket, reinterpret_cast<SOCKADDR*>( &sock_addr ), sizeof(sock_addr)) )
	{
		impl->Close();
		return;
	}
}

RWSocket::~RWSocket()
{
	delete impl; impl = 0;
}

bool RWSocket::is_valid()const
{
	return impl->is_valid();
}

//=====================================================
int RWSocket::read(void * data, int len)
{
	if( is_closed() )
	{
		impl->Close();
		return 0;
	}
/*	char * cata = static_cast<char *>( data );
	int left = len;
	// if data size is bigger then network buffer
	do
	{
		int res = recv( impl->socket, cata, left, 0 );
		if( res == SOCKET_ERROR || res == 0 )
		{
			return 0;
		}
		cata += res;
		left -= res;
	}while( left > 0 );

	return left ? 0 : len;*/
	char * cata = static_cast<char *>( data );

	unsigned long sz = 0;

	int res = ioctlsocket(impl->socket, FIONREAD, &sz);
	if( res == SOCKET_ERROR )
	{
		impl->Close();
		return 0;
	}
	res = sz ? recv( impl->socket, cata, std::min<int>(sz,len), 0 ) : 0;

	if( res == SOCKET_ERROR )
	{
		impl->Close();
		return 0;
	}
//	if( sz && len && res == 0 )
//		impl->Close();
	return res;
}

int	RWSocket::write(const void * data, int len)
{
	const char * cata = static_cast<const char *>( data );
	int left = len;
	// if data size is bigger then network buffer
	do
	{
		int res = send( impl->socket, cata, left, 0 );
		if( res == SOCKET_ERROR || res == 0 )
		{
			return 0;
		}
		cata += res;
		left -= res;
	}while( left > 0 );

	return len - left;
}

int RWSocket::bytes_available()const
{
	unsigned long sz = 0;

	int res = ioctlsocket(impl->socket, FIONREAD, &sz);
	if( res == SOCKET_ERROR )
	{
		impl->Close();
		return 0;
	}
	return sz;
}

bool RWSocket::is_closed()
{
	fd_set read_fds;
	timeval timeout={0,0};
	FD_ZERO(&read_fds);
	FD_SET(impl->socket,&read_fds);
	int res = select( 1, &read_fds, NULL, NULL, &timeout );
	if( res && bytes_available() == 0)
		return true;
	return false;
}


UDPSocket::UDPSocket(int port, bool server)
:	impl( new Impl() ),
	port(port)
{
	if( !impl->SystemValid() )
		return;

	impl->socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if( !impl->is_valid() )
		return;

//	set_blocking(false);

	// Fill address machinery of sockets.
	SOCKADDR_IN sock_addr;
	memset( &sock_addr, 0, sizeof(sock_addr) );
	sock_addr.sin_family = AF_INET;
	sock_addr.sin_port = server ? htons( (u_short)port ) : 0;

	hostent * host = gethostbyname("");
	sock_addr.sin_addr.s_addr = ((LPIN_ADDR)host->h_addr)->s_addr;

	if(bind(impl->socket,reinterpret_cast<sockaddr *>(&sock_addr), sizeof(sock_addr)))
	{
		impl->Close();
		return;
	}

	int val;
	int val_size = sizeof(val);
	getsockopt(impl->socket, SOL_SOCKET, SO_MAX_MSG_SIZE, (char *)&val, &val_size);

	impl->SetIntOption( SOL_SOCKET, SO_BROADCAST, 1 );
}

UDPSocket::~UDPSocket()
{
	delete impl; impl = 0;
}

bool UDPSocket::is_valid()const
{
	return impl->is_valid();
}

//=====================================================
int UDPSocket::broadcast(void * data, int len)
{
	char * cata = static_cast<char *>( data );
	struct sockaddr_in Recv_addr;   
	Recv_addr.sin_family = AF_INET;         
	Recv_addr.sin_port = htons(port);
	Recv_addr.sin_addr.s_addr = INADDR_BROADCAST; 
	return sendto(impl->socket, cata, len, 0, (sockaddr *)&Recv_addr, sizeof(Recv_addr));
}

int UDPSocket::read(void * data, int len)
{
	if( is_closed() )
	{
		impl->Close();
		return 0;
	}

	char * cata = static_cast<char *>( data );

	unsigned long sz = 0;

	int res = ioctlsocket(impl->socket, FIONREAD, &sz);
	if( res == SOCKET_ERROR )
	{
		impl->Close();
		return 0;
	}

	int size_struct = sizeof(struct sockaddr_in);
	res = sz ? recvfrom(impl->socket, cata, std::min<int>(sz,len), 0,(sockaddr *)&impl->last_sender, &size_struct) : 0;

	if( res == SOCKET_ERROR )
	{
		impl->Close();
		return 0;
	}

	return res;
}

int	UDPSocket::write(const void * data, int len)
{
	const char * cata = static_cast<const char *>( data );
	int left = len;
	// if data size is bigger then network buffer
	int res = sendto(impl->socket, cata, left, 0,(sockaddr *)&impl->last_sender,sizeof(impl->last_sender));
	if( res == SOCKET_ERROR || res == 0 )
	{
		return 0;
	}

	return res;
}

int UDPSocket::bytes_available()const
{
	unsigned long sz = 0;

	int res = ioctlsocket(impl->socket, FIONREAD, &sz);
	if( res == SOCKET_ERROR )
	{
		impl->Close();
		return 0;
	}
	return sz;
}

bool UDPSocket::is_closed()
{
	fd_set read_fds;
	timeval timeout={0,0};
	FD_ZERO(&read_fds);
	FD_SET(impl->socket,&read_fds);
	int res = select( 1, &read_fds, NULL, NULL, &timeout );
	if( res && bytes_available() == 0)
		return true;
	return false;
}

void UDPSocket::set_blocking(bool blocking)
{
	u_long flag = blocking ? 0 : 1;
	ioctlsocket(impl->socket, FIONBIO, &flag);
}

std::string UDPSocket::get_last_message_ip()const
{
	return std::string(inet_ntoa(impl->last_sender.sin_addr));
}

} // namespace net
} // namespace liba

